-- 2022-8-17

--[[ 18.0 迭代器和泛型for

    我们已经在本书中的几个需求中使用过泛型for 比如读取一个文件的所有行或遍历一个对象所有匹配的模式
    然而 我们仍然不知道如何创建迭代器
    在本章中 我们将学习这一部分内容 先从简单的迭代器入手 再学习如何利用泛型for的所有功能来编写各种各样的迭代器
]]

---[[ 18.1 迭代器和闭包

    -- 迭代器(iterator)是一种可以让我们遍历一个集合中所有元素的代码结构
    -- 在Lua语言中 通常使用函数表示迭代器:每一次调用函数时它都会返回标准输入中的下一行 在没有可以读取的行时返回nil

    -- 所有的迭代器都需要在连续的调用之间保存一些状态 这样才能知道当前迭代所处的位置及如何从当前位置步进到下一位置
    -- 对于函数io.read而言 C语言会将状态保存在流的结构体中 对于我们自己的迭代器而言 闭包则为保存状态提供了一种良好的机制
    -- 请注意 一个闭包就是一个可以访问其自身的环境中一个或多个局部变量的函数
    -- 这些变量将连续调用过程中的值并将其保存在闭包中 从而使得闭包能够记住迭代所处的位置
    -- 当然 要创建一个新的闭包 我们还必须创建非局部变量 
    -- 因此 一个闭包结构通常涉及俩个函数 闭包本身和一个用于创建该闭包及其封装变量的工厂(factory)

    -- 作为示例 让我们来为列表编写一个简单的迭代器 与ipairs不同的是 该迭代器并不是返回每个元素的索引而是返回元素的值:
    function values (t)
        local i = 0
        return function ()i = i + 1; return t[i] end
    end
    -- 在这个例子中 values就是工厂 每当调用这个工厂时 他就会创建一个新的闭包(即迭代器本身)
    -- 这个闭包将它的状态保存在其外部的变量t和i中 这两个变量也是由values创建的
    -- 每次调用这个迭代器时 他就从列表t中返回下一个值 在遍历完最后一个元素后 迭代器返回nil 表示迭代结束
    -- 我们可以在一个while循环中使用这个迭代器
    local t = {10,20,30}
    local iter = values(t)   -- 创建迭代器
    while true do
        local element = iter()  -- 调用迭代器
        if element == nil then break end
        print(element)
    end
    -- 不过 使用泛型for更简单 毕竟 泛型for正是为了这种迭代而设计的:
    local t = {10,20,30}
    for element in values(t) do
        print(element)
    end
    -- 泛型for为一次迭代循环做了所有的记录工作 它在内部保存了迭代函数 因此不需要变量iter
    -- 它在每次做新的迭代时都会再次调用迭代器 并在迭代器返回nil时结束循环(在下一节中 我们将会看到泛型for还完成了更多的工作)
    
    -- 下面是一个更高级的示例 示例18.1展示了一个迭代器 它可以遍历来自标准输入的所有单词
    -- 18.1 遍历来自标准输入的所有单词的迭代器
    function allwords ()
        local line = io.read() -- 当前行
        local pos = 1 -- 当前行的当前位置
        return function () -- 迭代函数
            while line do -- 当还有行时循环
                local w,e = string.match(line,"(%w+)()",pos)
                if w then -- 发现一个单词?
                    pos = e -- 下一个位置位于该单词后
                    return w -- 返回该单词
                else
                    line = io.read() -- 没找到单词 尝试下一行
                    pos = 1 -- 从第一个位置重新开始
                end
            end
            return nil -- 没有行了 迭代结束
        end
    end
    -- 为了完成这样的遍历 我们需要保存两个值:当前行的内容(变量line)及当前行的当前位置(变量pos)
    -- 有了这些数据 我们就可以不断产生下一个单词 这个迭代器函数的主要部分是调用函数string.match 以当前位置作为起始在当前行中搜索一个单词
    -- 函数string.match使用模式"%w+"来匹配一个"单词" 也就是匹配一个或多个字母/数字字符
    -- 如果函数string.match找到了一个单词 它就捕获返回这个单词及该单词之后的第一个字符的位置(一个空匹配) 迭代函数则更新当前位置并返回该单词
    -- 否则 迭代函数读取新的一行 然后重复上述反复搜索过程 在所有的行都被读取完后 迭代函数返回nil以表示迭代结束

    -- 尽管迭代器本身有点复杂 但allwords的使用还是很简明易懂的
    for word in allwords() do
        print(word)
    end

    -- 对于迭代器而言 一种常见的情况就是 编写迭代器可能不太容易 但使用迭代器却十分简单
    -- 这也不是一个大问题 因为使用Lua语言编程的最终用户一般不会去定义迭代器 而只会使用那些宿主应用已经提供的迭代器
--]]